package chip8

import (
	"code.google.com/p/go8/games"
	"time"
)

type GameRunner struct {
	interpreterFactory InterpreterFactory
	interpreter        Chip8Interpreter
	tickerFactory      TimedTickerFactory
	timer              TimedTicker
	quit               chan bool
	stopped            chan bool
}

type Chip8Interpreter interface {
	LoadProgramToAddress(prog []byte, address uint16)
	ExecuteNextInstruction()
	TimerTick()
	Screen() Screen
}

type TimedTicker interface {
	Start(interval time.Duration)
	Channel() <-chan time.Time
	Stop()
}

type TimedTickerFactory interface {
	New() TimedTicker
}

type InterpreterFactory interface {
	New() Chip8Interpreter
}

func NewRunner(f InterpreterFactory, tf TimedTickerFactory) *GameRunner {
	return &GameRunner{interpreterFactory: f, tickerFactory: tf}
}

func (r *GameRunner) Run(game *games.Game) {
	r.interpreter = r.interpreterFactory.New()
	r.interpreter.LoadProgramToAddress(game.Program, game.StartAddress)
	r.runProgram(game.ClockSpeed, game.InstructionsPerCycle)
	r.startChip8Timers()
}

func (r *GameRunner) runProgram(clockSpeed time.Duration, perCycle int) {
	clock := r.tickerFactory.New()
	clock.Start(clockSpeed)
	r.quit = make(chan bool)
	r.stopped = make(chan bool)
	go func() {
		running := true
		for running {
			select {
			case <-clock.Channel():
				for i := 0; i < perCycle; i++ {
					r.interpreter.ExecuteNextInstruction()
				}
			case <-r.quit:
				running = false
			}
		}
		r.timer.Stop()
		clock.Stop()
		r.stopped <- true
	}()
}

func (r *GameRunner) startChip8Timers() {
	r.timer = r.tickerFactory.New()
	r.timer.Start(1000000 / 60 * time.Microsecond)
	go func() {
		for _ = range r.timer.Channel() {
			r.interpreter.TimerTick()
		}
	}()
}

func (r *GameRunner) Quit() {
	r.quit <- true
	<-r.stopped
}

func (r *GameRunner) Screen() Screen {
	if r.interpreter == nil {
		return nil
	}
	return r.interpreter.Screen()
}
